// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "main.h"
#include "syscall.h"
#include "filesys.h"
#include "translate.h"
#include "thread.h"
#include "scheduler.h"
#include "addrspace.h"

#include "synch.h"

//----------------------------------------------------------------------
// ExceptionHandler
// 	Entry point into the Nachos kernel.  Called when a user program
//	is executing, and either does a syscall, or generates an addressing
//	or arithmetic exception.
//
// 	For system calls, the following is the calling convention:
//
// 	system call code -- r2
//		arg1 -- r4
//		arg2 -- r5
//		arg3 -- r6
//		arg4 -- r7
//
//	The result of the system call, if any, must be put back into r2. 
//
// And don't forget to increment the pc before returning. (Or else you'll
// loop making the same system call forever!
//
//	"which" is the kind of exception.  The list of possible exceptions 
//	are in machine.h.
//----------------------------------------------------------------------

Syscall* sys = new Syscall();

AddrSpace *currentSpace;
Thread *newThread;

Barrier *barrier = NULL;

int nrSleepingTh;

void f(void *arg) {
	char** argv = (char**)arg;
	int i = 0;
	while(argv[i] != NULL)
	{
		i++;
	}
	kernel->currentThread->space->Execute(argv[0], i, argv);
}

void
ExceptionHandler(ExceptionType which)
{
    int type = kernel->machine->ReadRegister(2);
    int arg1 = kernel->machine->ReadRegister(4);
    int arg2 = kernel->machine->ReadRegister(5);
    int arg3 = kernel->machine->ReadRegister(6);
    int arg4 = kernel->machine->ReadRegister(7);
    currentSpace = kernel->currentThread->space;
    char* buffer = (char*)malloc(MaxStringArgLength + 1);
	memset(buffer, 0, MaxStringArgLength + 1);
	int bufferLength;
	
    //Syscall* sys = new Syscall();
    switch (which) {
	case SyscallException:
	    switch(type) {
		case SC_Halt:
		    DEBUG(dbgAddr, "Shutdown, initiated by user program.\n");
		    kernel->interrupt->Halt();
		case SC_Exit:
			DEBUG(dbgAddr, "Exit, initiated by user program.\n");
			sys->Exit(arg1);
            		kernel->currentThread->Finish();
			free(buffer);
			return;
		case SC_Exec:
			DEBUG(dbgAddr, "Exec, initiated by user program.\n");
			if((bufferLength = currentSpace->UserStringToKernel(arg1, buffer)) != -1)
			{
				int i;
				int argc = arg3;
				char** argv = new char*[arg2 + 1];
				for(i = 0; i < arg2; i++)
				{
					int addr = 0;
					if(currentSpace->ReadMem(argc, 4, &addr) == OK)
					{
						argv[i] = (char*)malloc(MaxStringArgLength + 1);
						currentSpace->UserStringToKernel(addr, argv[i]);
					}
					argc += 4;
				}
				argv[arg2] = NULL;
				
				newThread  = new Thread(argv[0]);
				currentSpace = new AddrSpace();
				newThread->space = currentSpace;
				int pid = sys->Exec(newThread);
		                //return din exec in user space
                		kernel->machine->WriteRegister(2,pid);
				kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
				newThread->Fork(f, (void*)argv);
			} else
				kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			
			free(buffer);
			return;
		case SC_Join:
			DEBUG(dbgAddr, "Join, initiated by user program.\n");
			sys->Join(arg1);
        		kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			return;
		case SC_Create:
			DEBUG(dbgAddr, "Create, initiated by user program.\n");
			if((bufferLength = currentSpace->UserStringToKernel(arg1, buffer)) != -1) {
				sys->Create(buffer);
        		        printf("S-a creat fisierul \"%s\"\n", buffer);
			}		
			free(buffer);
	           	kernel->machine->WriteRegister(PCReg,kernel->machine->ReadRegister(NextPCReg));
			return;
		case SC_Open:
			DEBUG(dbgAddr, "Open, initiated by user program.\n");
			if((bufferLength = currentSpace->UserStringToKernel(arg1, buffer)) != -1)
			{
				int fd = sys->Open(buffer);
				if(fd != -1)
		                    printf("S-a deschis fisierul \"%s\", fd = %d\n", buffer, fd);
                		else 
		                    printf("Fisier inexistent \"%s\"!\n", buffer);
                    
                		kernel->machine->WriteRegister(2, fd);
			}
	
			free(buffer);
			kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			return;
		case SC_Read:
			DEBUG(dbgAddr, "Read, initiated by user program.\n");

			sys->Read(arg1, buffer, arg3);			
			if(currentSpace->KernelToUserBuf(arg2, arg3, buffer) != -1)
			{
	
				kernel->machine->WriteRegister(2, arg3);
			}

			free(buffer);
			kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			return;
		case SC_Write:
			DEBUG(dbgAddr, "Write, initiated by user program.\n");
			if ((bufferLength = currentSpace->UserBufToKernel(arg2, arg3, buffer)) != -1)
			{
				sys->Write(arg1, buffer, arg3);
			}
			
			free(buffer);
			kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			return;
		case SC_Close:
			DEBUG(dbgAddr, "Close, initiated by user program.\n");
			sys->Close(arg1);
			free(buffer);
			kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			free(buffer);
			return;
		case SC_BCreate:
			cout << "Barrier Create\n";
			if (barrier == NULL)
				barrier = new Barrier(arg2);
				
			kernel->machine->WriteRegister(2, 0);
				
			kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			free(buffer);
			return;
		case SC_BWait:
			cout << "Waiting barrier" << (barrier == NULL ? "yes" : "no") << endl;
			if (barrier == NULL) {
				kernel->machine->WriteRegister(2, -1);
				kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			} else {
				kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
				kernel->machine->WriteRegister(2, 0);
				barrier->wait();
				
			}
			free(buffer);
			return;

		// me
		case WAIT_UNTIL:
			DEBUG(dbgMe,"MY WAIT UNTIL");
			sys->WaitUntil(arg1);
			free(buffer);
			kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			return;
		case GET_NO_OF_THREADS:
			DEBUG(dbgMe,"MY GET NO OF THREADS");
			sys->getNoOfSleepingThreads();	
			printf("S-a gasit numarul de threaduri care asteapta ca urmare a apelurilor: WaitUntil");
			free(buffer);
			kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(NextPCReg));
			return;

		default:
		    cerr << "Unexpected system call " << type << "\n";
 		    break;
	    }
	    break;
	default:
	    cerr << "Unexpected user mode exception " << which << "\n";
	    break;
    }
    ASSERTNOTREACHED();
   
}
